package main

import (
	"fmt"
	"runtime"
	//"time"
)

//
// 1、主线程是一个物理线程, 直接作用在CPU上, 是重量级的, 非常耗费CPU资源
// 2、协程从主线程开启, 是轻量级的线程, 是逻辑态, 对资源消耗相对小
// 3、Golang的协程机制是重要的特点, 可以轻松的开启上万个协程
// 4、go demo1()  开启一个协程使用关键词go加上函数或方法, 如果主线程先完成, 则协程也会结束
// 5、在一个程序中如何知道是否存在资源竞争问题, 可以在编译程序的时候运行: go build -race xx.go
//    然后运行编译生成的可执行文件, 如果源代码有资源竞争问题, 可执行文件就会提示资源竞争错误

/*
一个Go主线程上(也可以称为进程), 可以起多个协程
      | -> 协程1
主线程 | -> 协程2
      | -> 协程3

Go协程的特点
• 有独立的栈空间
• 共享程序堆空间
• 调度由用户控制
• 协程是轻量级的线程
*/

func WriteChan(c chan int) {
	// c的容量是50, 所以当c里面填满50个数据后, 就会阻塞, 等待其他goroutine从c读数据
	for i := 0; i < 60; i++ {
		c <- i
		fmt.Println("WriteChan", len(c))
	}
	// 阻塞直到第60个数据写入c才会往下执行
	fmt.Println("out of for loop", len(c))
	close(c) // 不关闭会死锁
	fmt.Println("WriteChan over")
}

func ReadChan(i chan int, b chan bool) {
	for {
		v, ok := <-i
		if !ok {
			break
		}
		fmt.Println("ReadChan", v)
	}
	b <- true
}

func isPrime(num int) bool {
	if num == 1 {
		return false
	}
	half := num / 2
	for i := 2; i <= half; i++ {
		if num%i == 0 {
			return false
		}
	}
	return true
}

func WritePrime(numChan, primeChan chan int, quitChan chan bool) {
	for {
		num, ok := <-numChan
		if !ok {
			break
		}
		if isPrime(num) {
			primeChan <- num
		}
	}
	quitChan <- true
}

func main() {
	// 获取当前操作系统的逻辑cpu核数
	fmt.Println(runtime.NumCPU())
	// 设置最大使用的逻辑cpu核数
	runtime.GOMAXPROCS(4)

	// 1、无缓冲通道
	// 创建完无缓冲通道后不能在该无缓冲通道的goroutine下写、读数据, 读写操作必须在不同的goroutine下执行
	// main goroutine会阻塞在第66行等待其他goroutine读数据, 直到匿名goroutine在第63行准备读数据
	var unBufferedChan = make(chan int)
	go func(c chan int) {
		v := <-c // 程序在这里阻塞, 直到有其他goroutine准备写入数据
		fmt.Println(v)
	}(unBufferedChan)
	unBufferedChan <- 100 // 程序在这里阻塞, 不会往下执行, 直到有其他goroutine准备读取数据

	//close(unBufferedChan)  // 关闭chan
	//
	//go func(c chan int) {  // 该匿名goroutine不会被执行, 因为第73行的main goroutine已经执行完毕
	//	c <- 101
	//}(unBufferedChan)
	//fmt.Println(<-unBufferedChan)  // 0  因为chan被关闭了, 所以可以从chan里读出零值
	/*
		假如main goroutine等待1秒, 让匿名goroutine有机会运行, 此时对于已经关闭的chan, 往里面写数据会panic: send on closed channel
		go func(c chan int) {
		    c <- 101
		}(unBufferedChan)
		time.Sleep(time.Second)
		fmt.Println(<-unBufferedChan)
	*/

	// 2、有缓冲通道
	// 创建完有缓冲通道后可以在该有缓冲通道所在的goroutine下写数据, 只要不超过它的容量
	// 也可以在该有缓冲通道所在的goroutine下读数据, 只要它里面数据
	var bufferedChan = make(chan int, 1)
	bufferedChan <- 1
	close(bufferedChan)
	fmt.Println(<-bufferedChan) // 1
	fmt.Println(<-bufferedChan) // 0
	fmt.Println(<-bufferedChan) // 0
	/*
		创建完有缓冲通道直接在该goroutine读数据的错误示例(没有其他goroutine往通道写数据的前提下)
		var bufferedChan = make(chan int, 1)
		fmt.Println(<-bufferedChan)  // 有缓冲通道里还没有任何数据, 直接读会panic: send on closed channel
		创建完有缓冲通道直接在该goroutine读数据的正确示例
		var bufferedChan = make(chan int, 1)
		close(bufferedChan)
		fmt.Println(<-bufferedChan)  // 0
	*/

	data := make(chan int, 50)
	exit := make(chan bool, 1) // 写不写容量1都可以
	go WriteChan(data)
	go ReadChan(data, exit)

	stop := <-exit // main goroutine会阻塞在这里, 因为exit里此时还没有任何数据
	fmt.Println(stop)

	// 若exit没有被关闭, 下面代码就会死锁, 因为第108行已经取走里exit的数据, 此时exit里为空
	//for v := range exit {
	//	fmt.Println(111, v)
	//	if v {
	//		break
	//	}
	//}

	var numChan = make(chan int)
	var primeChan = make(chan int)
	var quitCHan = make(chan bool)

	go func(c chan int, n int) {
		for i := 1; i <= n; i++ {
			c <- i
		}
		close(c)
	}(numChan, 10000)

	for i := 0; i < 4; i++ {
		go WritePrime(numChan, primeChan, quitCHan)
	}

	go func(c chan bool) {
		for i := 0; i < 4; i++ {
			<-c
		}
		close(primeChan) // 4个写成全部计算完才可以关闭chan
	}(quitCHan)

	// 打印primeChan方式1
	//for {
	//	//	prime, ok := <-primeChan
	//	//	if !ok {
	//	//		break
	//	//	}
	//	//	fmt.Println(prime)
	//	//}

	// 打印primeChan方式2
	for v := range primeChan {
		fmt.Println(v)
	}
}
